﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/* Average.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;

#endregion

#nullable enable

namespace AM.Memory.Collections.Linq;

public static partial class PoolingEnumerable
{
    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double Average (this IPoolingEnumerable<int> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (var enumerator = source.GetEnumerator())
        {
            if (!enumerator.MoveNext())
            {
                // throw new InvalidOperationException("Sequence contains no elements");
            }

            long sum = enumerator.Current;
            long count = 1;
            checked
            {
                while (enumerator.MoveNext())
                {
                    sum += enumerator.Current;
                    ++count;
                }
            }

            return (double)sum / count;
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double? Average (this IPoolingEnumerable<int?> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (var e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                int? v = e.Current;
                if (v.HasValue)
                {
                    long sum = v.GetValueOrDefault();
                    long count = 1;
                    checked
                    {
                        while (e.MoveNext())
                        {
                            v = e.Current;
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }
                    }

                    return (double)sum / count;
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double Average (this IPoolingEnumerable<long> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (var e = source.GetEnumerator())
        {
            if (!e.MoveNext())
            {
                throw new InvalidOperationException ("Sequence contains no elements");
            }

            long sum = e.Current;
            long count = 1;
            checked
            {
                while (e.MoveNext())
                {
                    sum += e.Current;
                    ++count;
                }
            }

            return (double)sum / count;
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double? Average (this IPoolingEnumerable<long?> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (IPoolingEnumerator<long?> e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                long? v = e.Current;
                if (v.HasValue)
                {
                    long sum = v.GetValueOrDefault();
                    long count = 1;
                    checked
                    {
                        while (e.MoveNext())
                        {
                            v = e.Current;
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }
                    }

                    return (double)sum / count;
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static float Average (this IPoolingEnumerable<float> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (IPoolingEnumerator<float> e = source.GetEnumerator())
        {
            if (!e.MoveNext())
            {
                throw new InvalidOperationException ("Sequence contains no elements");
            }

            double sum = e.Current;
            long count = 1;
            while (e.MoveNext())
            {
                sum += e.Current;
                ++count;
            }

            return (float)(sum / count);
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static float? Average (this IPoolingEnumerable<float?> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (IPoolingEnumerator<float?> e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                float? v = e.Current;
                if (v.HasValue)
                {
                    double sum = v.GetValueOrDefault();
                    long count = 1;
                    checked
                    {
                        while (e.MoveNext())
                        {
                            v = e.Current;
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }
                    }

                    return (float)(sum / count);
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double Average (this IPoolingEnumerable<double> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (IPoolingEnumerator<double> e = source.GetEnumerator())
        {
            if (!e.MoveNext())
            {
                throw new InvalidOperationException ("Sequence contains no elements");
            }

            double sum = e.Current;
            long count = 1;
            while (e.MoveNext())
            {
                // There is an opportunity to short-circuit here, in that if e.Current is
                // ever NaN then the result will always be NaN. Assuming that this case is
                // rare enough that not checking is the better approach generally.
                sum += e.Current;
                ++count;
            }

            return sum / count;
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double? Average (this IPoolingEnumerable<double?> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (IPoolingEnumerator<double?> e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                double? v = e.Current;
                if (v.HasValue)
                {
                    double sum = v.GetValueOrDefault();
                    long count = 1;
                    checked
                    {
                        while (e.MoveNext())
                        {
                            v = e.Current;
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }
                    }

                    return sum / count;
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static decimal Average (this IPoolingEnumerable<decimal> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (IPoolingEnumerator<decimal> e = source.GetEnumerator())
        {
            if (!e.MoveNext())
            {
                throw new InvalidOperationException ("Sequence contains no elements");
            }

            decimal sum = e.Current;
            long count = 1;
            while (e.MoveNext())
            {
                sum += e.Current;
                ++count;
            }

            return sum / count;
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static decimal? Average (this IPoolingEnumerable<decimal?> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        using (IPoolingEnumerator<decimal?> e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                decimal? v = e.Current;
                if (v.HasValue)
                {
                    decimal sum = v.GetValueOrDefault();
                    long count = 1;
                    while (e.MoveNext())
                    {
                        v = e.Current;
                        if (v.HasValue)
                        {
                            sum += v.GetValueOrDefault();
                            ++count;
                        }
                    }

                    return sum / count;
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double Average<TSource> (this IPoolingEnumerable<TSource> source, Func<TSource, int> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (var e = source.GetEnumerator())
        {
            if (!e.MoveNext())
            {
                throw new InvalidOperationException ("Sequence contains no elements");
            }

            long sum = selector (e.Current);
            long count = 1;
            checked
            {
                while (e.MoveNext())
                {
                    sum += selector (e.Current);
                    ++count;
                }
            }

            return (double)sum / count;
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double? Average<TSource> (this IPoolingEnumerable<TSource> source, Func<TSource, int?> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (var e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                int? v = selector (e.Current);
                if (v.HasValue)
                {
                    long sum = v.GetValueOrDefault();
                    long count = 1;
                    checked
                    {
                        while (e.MoveNext())
                        {
                            v = selector (e.Current);
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }
                    }

                    return (double)sum / count;
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double Average<TSource> (this IPoolingEnumerable<TSource> source, Func<TSource, long> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (var e = source.GetEnumerator())
        {
            if (!e.MoveNext())
            {
                throw new InvalidOperationException ("Sequence contains no elements");
            }

            long sum = selector (e.Current);
            long count = 1;
            checked
            {
                while (e.MoveNext())
                {
                    sum += selector (e.Current);
                    ++count;
                }
            }

            return (double)sum / count;
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double? Average<TSource> (this IPoolingEnumerable<TSource> source, Func<TSource, long?> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (var e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                long? v = selector (e.Current);
                if (v.HasValue)
                {
                    long sum = v.GetValueOrDefault();
                    long count = 1;
                    checked
                    {
                        while (e.MoveNext())
                        {
                            v = selector (e.Current);
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }
                    }

                    return (double)sum / count;
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static float Average<TSource> (this IPoolingEnumerable<TSource> source, Func<TSource, float> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
        {
            if (!e.MoveNext())
            {
                throw new InvalidOperationException ("Sequence contains no elements");
            }

            double sum = selector (e.Current);
            long count = 1;
            while (e.MoveNext())
            {
                sum += selector (e.Current);
                ++count;
            }

            return (float)(sum / count);
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static float? Average<TSource> (this IPoolingEnumerable<TSource> source, Func<TSource, float?> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                float? v = selector (e.Current);
                if (v.HasValue)
                {
                    double sum = v.GetValueOrDefault();
                    long count = 1;
                    checked
                    {
                        while (e.MoveNext())
                        {
                            v = selector (e.Current);
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }
                    }

                    return (float)(sum / count);
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double Average<TSource> (this IPoolingEnumerable<TSource> source, Func<TSource, double> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
        {
            if (!e.MoveNext())
            {
                throw new InvalidOperationException ("Sequence contains no elements");
            }

            double sum = selector (e.Current);
            long count = 1;
            while (e.MoveNext())
            {
                // There is an opportunity to short-circuit here, in that if e.Current is
                // ever NaN then the result will always be NaN. Assuming that this case is
                // rare enough that not checking is the better approach generally.
                sum += selector (e.Current);
                ++count;
            }

            return sum / count;
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static double? Average<TSource> (this IPoolingEnumerable<TSource> source,
        Func<TSource, double?> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                double? v = selector (e.Current);
                if (v.HasValue)
                {
                    double sum = v.GetValueOrDefault();
                    long count = 1;
                    checked
                    {
                        while (e.MoveNext())
                        {
                            v = selector (e.Current);
                            if (v.HasValue)
                            {
                                sum += v.GetValueOrDefault();
                                ++count;
                            }
                        }
                    }

                    return sum / count;
                }
            }
        }

        return null;
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static decimal Average<TSource> (this IPoolingEnumerable<TSource> source,
        Func<TSource, decimal> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
        {
            if (!e.MoveNext())
            {
                throw new InvalidOperationException ("Sequence contains no elements");
            }

            decimal sum = selector (e.Current);
            long count = 1;
            while (e.MoveNext())
            {
                sum += selector (e.Current);
                ++count;
            }

            return sum / count;
        }
    }

    /// <summary>
    /// Calculates avg of all given numbers. Complexity = O(N)
    /// </summary>
    public static decimal? Average<TSource> (this IPoolingEnumerable<TSource> source,
        Func<TSource, decimal?> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException (nameof (source));
        }

        if (selector == null)
        {
            throw new ArgumentNullException (nameof (selector));
        }

        using (IPoolingEnumerator<TSource> e = source.GetEnumerator())
        {
            while (e.MoveNext())
            {
                decimal? v = selector (e.Current);
                if (v.HasValue)
                {
                    decimal sum = v.GetValueOrDefault();
                    long count = 1;
                    while (e.MoveNext())
                    {
                        v = selector (e.Current);
                        if (v.HasValue)
                        {
                            sum += v.GetValueOrDefault();
                            ++count;
                        }
                    }

                    return sum / count;
                }
            }
        }

        return null;
    }
}
